#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#define DCMD_AUDIO_SET_CHANNEL_MONO 1
#define DCMD_AUDIO_SET_CHANNEL_STEREO 2
#define DCMD_AUDIO_SET_SAMPLE_RATE_CD 3
#define DCMD_AUDIO_SET_SAMPLE_RATE_DAT 4
int io_devctl(resmgr_context_t *ctp, io_devctl_t *msg,
iofunc_ocb_t *ocb) {
int sts;
// 1) Проверить, не является ли это обычным
// POSIX-совместимым devctl()
if ((sts =
iofunc_devctl_default(ctp, msg, ocb)) !=
_RESMGR_DEFAULT) {
return (sts);
}
// 2) Узнать, что за команда, и отработать ее
switch (msg->i.dcmd) {
case DCMD_AUDIO_SET_CHANNEL_MONO:
audio_set_nchannels(1);
break;
case DCMD_AUDIO_SET_CHANNEL_STEREO:
audio_set_nchannels(2);
break;
case DCMD_AUDIO_SET_SAMPLE_RATE_CD:
audio_set_samplerate(44100);
break;
case DCMD_AUDIO_SET_SAMPLE_RATE_DAT:
audio_set_samplerate(48000);
break;
// 3) Если мы не знаем такой команды, отвергнуть ее
default:
return (ENOSYS);
}
// 4) Сказать клиенту, что все отработано
memset(imsg->о, 0, sizeof(msg->о));
SETIOV(ctp->iov, &msg->o, sizeof(msg->o));
return (_RESMGR_NPARTS(1));
}
Этап 1
На первом этапе мы снова видим применение вспомогательной функции, на этот раз — функции iofunc_devctl_default(), которая используется для выполнения всей обработки по умолчанию для devctl(). Если вы не поставляете свою версию io_devctl(), а только инициализируете таблицы функций ввода/вывода и установления соединения при помощи iofunc_func_init(), будет вызвана именно iofunc_devctl_default(). Мы включаем ее в нашу версию io_devctl(), потому что мы хотим, чтобы она обработала для нас все стандартные POSIX-варианты вызова devctl(). Затем мы проверяем возвращаемое значение; если это не _RESMGR_DEFAULT, значит, функция iofunc_devctl_default() «обработала» запрос, и нам остается только возвратить это значение, выдав его за «наше».
Если возвращенное значение является константой _RESMGR_DEFAULT, это говорит нам, что вспомогательная функция не обработала запрос, и что мы должны выяснить, является ли он одним из «наших».
Этап 2
Эта проверка выполняется на этапе 2 при помощи инструкции switch/case. Мы просто проверяем значение dcmd, которое клиентский код указал во втором параметре функции devctl(), на предмет совпадения с какой-нибудь из «наших» команд. Обратите внимание, что для выполнения фактической «работы» для клиента мы вызываем фиктивные функции audio_set_nchannels() и audio_set_samplerate(). Здесь важно отметить, что мы преднамеренно избегаем обсуждения области данных функции devctl(). Вы можете подумать: «А что если я хочу установить частоту дискретизации в некое значение n? Как это сделать?» На этот вопрос мы ответим в следующем примере io_devctl(), который представлен ниже.
Этап 3
Этот этап — дань концепции защитного программирования. Мы возвращаем код ошибки ENOSYS, чтобы сообщить клиенту, что мы не распознали его запрос.
Этап 4
Наконец, мы обнуляем возвратную структуру и устанавливаем на нее одноэлементный вектор ввода/вывода. Затем мы возвращаем библиотеке администратора ресурсов единицу (1) через макрос _RESMGR_NPARTS(), сообщая ей тем самым, что мы возвращаем одноэлементный вектор ввода/вывода. Это и будет возвращено клиенту. Как вариант, мы могли бы применить макрос _RESMGR_PTR():
// Вместо этого
// 4) Сказать клиенту, что это сработало
memset(imsg->о, 0, sizeof(msg->о));
SETIOV(&ctp->iov, &msg->о, sizeof(msg->o));
return (_RESMGR_NPARTS(1));
//Мы могли бы сделать так:
// 4) Сказать клиенту, что это сработало
memset(imsg->о, 0, sizeof(msg->о));
return (_RESMGR_PTR(ctp, imsg->o, sizeof(msg->o)));
Причиной тому, что мы здесь обнулили возвращаемую структуру (вспомните, в примерах io_read() и io_write() мы этого не делали) является то, что в данном случае возвращаемая структура имеет реальное содержимое! (В случае с io_read() мы возвращали только собственно данные и число считанных байт — никакой «возвращаемой структуры» не было; в случае же с io_write() единственным возвращаемым значением было число записанных байт.)
Пример функции io_devctl(), имеющей дело с данными
В предыдущем примере io_devctl() мы подняли вопрос о том, как устанавливать произвольные значения частоты дискретизации, Очевидно, создание большого количества констант DCMD_AUDIO_SET_SAMPLE_RATE_* было бы не самым оптимальным решением — у нас бы просто не хватило разрядности поля dcmd.
С клиентской стороны мы будем использовать указатель на частоту дискретизации, dev_data_ptr, которую мы просто передадим как целое, поэтому поле nbytes будет содержать число байт в целом числе (для 32-разрядных машин это 4). Будем предполагать, что для этих целей определена константа DCMD_AUDIO_SET_SAMPLE_RATE.
Также неплохо было бы уметь считывать текущее значение частоты дискретизации. Для этого мы также будем использовать переменные dev_data_ptr и nbytes, как описано выше, но в обратном направлении — администратор ресурсов запишет nbytes данных в ячейку памяти, на которую указывает dev_data_ptr, вместо чтения данных из этой ячейки. Предположим, что для этого определена константа DCMD_AUDIO_GET_SAMPLE_RATE.
Давайте посмотрим, что в таком случае происходит в функции io_devctl() администратора ресурсов (того, что обсуждалось в предыдущем примере, мы здесь касаться не будем):
/*
* io_devctl2.с
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <devctl.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#define DCMD_AUDIO_SET_SAMPLE_RATE 1
#define DCMD_AUDIO_GET_SAMPLE_RATE 2
int io_devctl(resmgr_context_t *ctp, io_devctl_t *msg,
iofunc_ocb_t *ocb) {
int sts;
void *data;
int nbytes;
if ((sts =
iofunc_devctl_default(ctp, msg, ocb)) !=
_RESMGR_DEFAULT) {
return (sts);
}
// 1) Установить указатель на область данных сообщения
data = _DEVCTL_DATA(msg);
// 2) Установить число возвращаемых байт в 0
nbytes = 0;
// Проверить все команды; покажем только те, которые нам
// здесь интересны
switch (msg->i.dcmd) {
...
// 3) Обработать команду SET
case DCMD_AUDIO_SET_SAMPLE_RATE:
audio_set_samplerate(*(int*)data);
break;
// 4) Обработать команду GET
case DCMD_AUDIO_GET_SAMPLE_RATE:
*(int*)data = audio_get_samplerate();
nbytes = sizeof(int);
break;
...
}
// 5) Возвратить данные (если есть) клиенту
memset(&msg->о, 0, sizeof(msg->о));
msg->о.nbytes = nbytes;
SETIOV(ctp->iov, &msg->o, sizeof(msg->o) + nbytes);
return (_RESMGR_NPARTS(1));
}
Этап 1
В «шапке» мы декларировали указатель типа void* по имени data («данные»), которые мы будем использовать в качестве универсального указателя на область данных. Если вы обратитесь к приведенному выше описанию io_devctl(), то вы увидите, что структура данных состоит из объединения заголовков входной и выходной структур, за которым неявно следует область данных. На этапе 1 указатель на эту область данных возвращается макросом _DEVCTL_DATA().
Этап 2
Здесь мы должны указать, сколько байт мы собираемся возвратить клиенту. Я для удобства обнулил переменную nbytes перед выполнением каких-либо действий — теперь мне не придется принудительно обнулять ее в каждой ветви switch/case.
Этап 3
Пришло время для команды «set» («установить»). Мы вызываем фиктивную функцию audio_set_samplerate() и передаем ей значение частоты дискретизации, полученное разыменованием указателя data (который мы коварно выставили указателем на целое число... нет, никакого коварства, обычный для Си прием приведения типов). Это ключевой механизм, потому что это и есть наш способ «интерпретации» области данных (клиентского указателя dev_data_ptr) в соответствии с командой. В более сложном случае вы, наверное, выполнили бы приведение его типа к какой-нибудь структуре побольше вместо простого целого числа. Очевидно, что описания этой структуры на стороне как клиента, так и администратора ресурсов, должны быть идентичными, поэтому лучшим местом для описания такой структуры является заголовочный файл, в котором хранятся ваши командные константы DCMD_*