[#59551] ExternalControl: Add ADC support
diff --git a/src/Infrastructure b/src/Infrastructure
index 39fab07..dba5b21 160000
--- a/src/Infrastructure
+++ b/src/Infrastructure
@@ -1 +1 @@
-Subproject commit 39fab0786e6da658dd709f1691803e9274f6cea8
+Subproject commit dba5b2134eb75b659468a8c98bc2de7b9631f558
diff --git a/src/Renode/Network/ExternalControl/ADC.cs b/src/Renode/Network/ExternalControl/ADC.cs
new file mode 100644
index 0000000..cc8a9ce
--- /dev/null
+++ b/src/Renode/Network/ExternalControl/ADC.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) 2010-2024 Antmicro
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+using System;
+using System.Collections.Generic;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Sensor;
+using Antmicro.Renode.Utilities;
+
+namespace Antmicro.Renode.Network.ExternalControl
+{
+    public class ADC : BaseCommand, IInstanceBasedCommand<IADC>
+    {
+        public ADC(ExternalControlServer parent)
+            : base(parent)
+        {
+            Instances = new InstanceCollection<IADC>();
+        }
+
+        public override Response Invoke(List<byte> data) => this.InvokeHandledWithInstance(data);
+
+        public Response Invoke(IADC instance, List<byte> data)
+        {
+            if(data.Count < 1)
+            {
+                return Response.CommandFailed(Identifier, $"Expected at least {1 + InstanceBasedCommandHeaderSize} bytes of payload");
+            }
+            var command = (ADCCommand)data[0];
+
+            var expectedCount = GetExpectedPayloadCount(command);
+            if(expectedCount != data.Count)
+            {
+                return Response.CommandFailed(Identifier, $"Expected {expectedCount + InstanceBasedCommandHeaderSize} bytes of payload");
+            }
+
+            switch(command)
+            {
+                case ADCCommand.GetCount:
+                    var channelCount = instance.ADCChannelCount;
+                    parent.Log(LogLevel.Debug, "Executing ADC GetCount command, returned {0}", channelCount);
+                    return Response.Success(Identifier, channelCount.AsRawBytes());
+
+                case ADCCommand.GetValue:
+                    DecodeChannelArgument(data, out var channel);
+                    var value = instance.GetADCValue(channel);
+                    parent.Log(LogLevel.Debug, "Executing ADC GetValue command, channel #{0} returned {1}", channel, value);
+                    return Response.Success(Identifier, value.AsRawBytes());
+
+                case ADCCommand.SetValue:
+                    DecodeSetValueArguments(data, out channel, out value);
+                    parent.Log(LogLevel.Debug, "Executing ADC SetValue command, channel #{0} set to {1}", channel, value);
+                    instance.SetADCValue(channel, value);
+                    return Response.Success(Identifier);
+
+                default:
+                    return Response.CommandFailed(Identifier, "Unexpected command format");
+            }
+        }
+
+        public InstanceCollection<IADC> Instances { get; }
+
+        public override Command Identifier => Command.ADC;
+        public override byte Version => 0x0;
+
+        private int GetExpectedPayloadCount(ADCCommand command)
+        {
+            switch(command)
+            {
+                case ADCCommand.GetValue:
+                    return sizeof(byte) + sizeof(uint);
+                case ADCCommand.SetValue:
+                    return sizeof(byte) + sizeof(uint) * 2;
+                default:
+                    return sizeof(byte);
+            }
+        }
+
+        private void DecodeChannelArgument(List<byte> data, out int channel)
+        {
+            channel = BitConverter.ToInt32(data.GetRange(1, sizeof(uint)).ToArray(), 0);
+        }
+
+        private void DecodeSetValueArguments(List<byte> data, out int channel, out uint value)
+        {
+            DecodeChannelArgument(data, out channel);
+            value = BitConverter.ToUInt32(data.GetRange(5, sizeof(uint)).ToArray(), 0);
+        }
+
+        private const int InstanceBasedCommandHeaderSize = IInstanceBasedCommandExtensions.HeaderSize;
+
+        private enum ADCCommand : byte
+        {
+            GetCount = 0,
+            GetValue,
+            SetValue,
+        }
+    }
+}
diff --git a/src/Renode/Network/ExternalControl/ExternalControlServer.cs b/src/Renode/Network/ExternalControl/ExternalControlServer.cs
index 7a11a30..b2dc9c9 100644
--- a/src/Renode/Network/ExternalControl/ExternalControlServer.cs
+++ b/src/Renode/Network/ExternalControl/ExternalControlServer.cs
@@ -36,6 +36,7 @@
             commandHandlers = new CommandHandlerCollection();
             commandHandlers.Register(new RunFor(this));
             commandHandlers.Register(new GetTime(this));
+            commandHandlers.Register(new ADC(this));
 
             var getMachineHandler = new GetMachine(this);
             Machines = getMachineHandler;
diff --git a/src/Renode/Network/ExternalControl/ICommand.cs b/src/Renode/Network/ExternalControl/ICommand.cs
index d7e8e54..3410b94 100644
--- a/src/Renode/Network/ExternalControl/ICommand.cs
+++ b/src/Renode/Network/ExternalControl/ICommand.cs
@@ -13,6 +13,7 @@
         RunFor = 1,
         GetTime,
         GetMachine,
+        ADC,
     }
 
     public interface ICommand
diff --git a/src/Renode/Renode.csproj b/src/Renode/Renode.csproj
index 6001409..0cab438 100644
--- a/src/Renode/Renode.csproj
+++ b/src/Renode/Renode.csproj
@@ -137,6 +137,7 @@
     <Compile Include="Network\ExternalControl\GetTime.cs" />
     <Compile Include="Network\ExternalControl\IInstanceBasedCommand.cs" />
     <Compile Include="Network\ExternalControl\GetMachine.cs" />
+    <Compile Include="Network\ExternalControl\ADC.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
diff --git a/tools/external_control_client/include/renode_api.h b/tools/external_control_client/include/renode_api.h
index 0993434..0177eeb 100644
--- a/tools/external_control_client/include/renode_api.h
+++ b/tools/external_control_client/include/renode_api.h
@@ -33,6 +33,7 @@
 // Internals of these structs aren't part of the API.
 typedef struct renode renode_t;
 typedef struct renode_machine renode_machine_t;
+typedef struct renode_adc renode_adc_t;
 
 renode_error_t *renode_connect(const char *port, renode_t **renode);
 renode_error_t *renode_disconnect(renode_t **renode);
@@ -51,3 +52,10 @@
 
 renode_error_t *renode_run_for(renode_t *renode, renode_time_unit_t unit, uint64_t value);
 renode_error_t *renode_get_current_time(renode_t *renode_instance, renode_time_unit_t unit, uint64_t *current_time);
+
+/* ADC */
+
+renode_error_t *renode_get_adc(renode_machine_t *machine, const char *name, renode_adc_t **adc);
+renode_error_t *renode_get_adc_channel_count(renode_adc_t *adc, int32_t *count);
+renode_error_t *renode_get_adc_channel_value(renode_adc_t *adc, int32_t channel, uint32_t *value);
+renode_error_t *renode_set_adc_channel_value(renode_adc_t *adc, int32_t channel, uint32_t value);
diff --git a/tools/external_control_client/lib/renode_api.c b/tools/external_control_client/lib/renode_api.c
index 36f8be1..372f8b0 100644
--- a/tools/external_control_client/lib/renode_api.c
+++ b/tools/external_control_client/lib/renode_api.c
@@ -29,6 +29,11 @@
     int32_t md;
 };
 
+struct renode_adc {
+    renode_machine_t *machine;
+    int32_t id;
+};
+
 #define SERVER_START_COMMAND "emulation CreateExternalControlServer \"<NAME>\""
 #define SOCKET_INVALID -1
 
@@ -127,6 +132,7 @@
     RUN_FOR = 1,
     GET_TIME,
     GET_MACHINE,
+    ADC,
 } api_command_t;
 
 static uint8_t command_versions[][2] = {
@@ -134,6 +140,7 @@
     { RUN_FOR, 0x0 },
     { GET_TIME, 0x0 },
     { GET_MACHINE, 0x0 },
+    { ADC, 0x0 },
 };
 
 static renode_error_t *write_or_fail(int socket_fd, const uint8_t *data, ssize_t count)
@@ -415,6 +422,28 @@
     return NO_ERROR;
 }
 
+static renode_error_t *renode_get_instance_descriptor(renode_machine_t *machine, api_command_t api_command, const char *name, int32_t *instance_descriptor)
+{
+    uint32_t name_length = strlen(name);
+    uint32_t data_size = name_length + sizeof(int32_t) * 3;
+    int32_t *data __attribute__ ((__cleanup__(xcleanup))) = xmalloc(data_size);
+
+    data[0] = -1;
+    data[1] = machine->md;
+    data[2] = name_length;
+    memcpy(data + 3, name, name_length);
+
+    return_error_if_fails(renode_execute_command(machine->renode, api_command, data, data_size, data_size, &data_size));
+
+    assert_msg(data_size == 4, "received unexpected number of bytes");
+
+    *instance_descriptor = data[0];
+
+    assert_msg(*instance_descriptor >= 0, "received invalid instance descriptor");
+
+    return NO_ERROR;
+}
+
 struct run_for_out {
     uint64_t microseconds;
 };
@@ -463,3 +492,99 @@
 
     return NO_ERROR;
 }
+
+renode_error_t *renode_get_adc(renode_machine_t *machine, const char *name, renode_adc_t **adc)
+{
+    int32_t id;
+    return_error_if_fails(renode_get_instance_descriptor(machine, ADC, name, &id));
+
+    *adc = xmalloc(sizeof(renode_adc_t));
+    (*adc)->machine = machine;
+    (*adc)->id = id;
+
+    return NO_ERROR;
+}
+
+typedef enum {
+    GET_CHANNEL_COUNT = 0,
+    GET_CHANNEL_VALUE,
+    SET_CHANNEL_VALUE,
+} adc_command_t;
+
+typedef union {
+    struct {
+        int32_t id;
+        int8_t command;
+        int32_t channel;
+        uint32_t value;
+    } __attribute__((packed)) out;
+
+    struct {
+        int32_t count;
+    } get_count_result;
+
+    struct {
+        uint32_t value;
+    } get_value_result;
+} adc_frame_t;
+
+renode_error_t *renode_get_adc_channel_count(renode_adc_t *adc, int32_t *count)
+{
+    // adc id, adc command -> count
+    adc_frame_t frame = {
+        .out = {
+            .id = adc->id,
+            .command = GET_CHANNEL_COUNT,
+        },
+    };
+
+    uint32_t response_size;
+    return_error_if_fails(renode_execute_command(adc->machine->renode, ADC, &frame, sizeof(frame), offsetof(adc_frame_t, out.channel), &response_size));
+
+    assert_msg(response_size == sizeof(*count), "Received unexpected number of bytes");
+
+    *count = frame.get_count_result.count;
+
+    return NO_ERROR;
+}
+
+renode_error_t *renode_get_adc_channel_value(renode_adc_t *adc, int32_t channel, uint32_t *value)
+{
+    // adc id, adc command, channel index -> value
+    adc_frame_t frame = {
+        .out = {
+            .id = adc->id,
+            .command = GET_CHANNEL_VALUE,
+            .channel = channel,
+        },
+    };
+
+    uint32_t response_size;
+    return_error_if_fails(renode_execute_command(adc->machine->renode, ADC, &frame, sizeof(frame), offsetof(adc_frame_t, out.value), &response_size));
+
+    assert_msg(response_size == sizeof(*value), "Received unexpected number of bytes");
+
+    *value = frame.get_value_result.value;
+
+    return NO_ERROR;
+}
+
+renode_error_t *renode_set_adc_channel_value(renode_adc_t *adc, int32_t channel, uint32_t value)
+{
+    // adc id, adc command, channel index, value -> ()
+    adc_frame_t frame = {
+        .out = {
+            .id = adc->id,
+            .command = SET_CHANNEL_VALUE,
+            .channel = channel,
+            .value = value,
+        },
+    };
+
+    uint32_t response_size;
+    return_error_if_fails(renode_execute_command(adc->machine->renode, ADC, &frame, sizeof(frame), sizeof(frame.out), &response_size));
+
+    assert_msg(response_size == 0, "Received unexpected number of bytes");
+
+    return NO_ERROR;
+}