openMSX
CartridgeSlotManager.cc
Go to the documentation of this file.
2 #include "MSXMotherBoard.hh"
3 #include "HardwareConfig.hh"
4 #include "CommandException.hh"
5 #include "FileContext.hh"
6 #include "TclObject.hh"
7 #include "MSXException.hh"
8 #include "CliComm.hh"
9 #include "unreachable.hh"
10 #include "one_of.hh"
11 #include "outer.hh"
12 #include "StringOp.hh"
13 #include "xrange.hh"
14 #include <cassert>
15 #include <memory>
16 
17 using std::string;
18 using std::vector;
19 
20 namespace openmsx {
21 
22 // CartridgeSlotManager::Slot
23 CartridgeSlotManager::Slot::~Slot()
24 {
25  assert(!config);
26  assert(useCount == 0);
27 }
28 
30 {
31  return cartCommand != nullptr;
32 }
33 
34 bool CartridgeSlotManager::Slot::used(const HardwareConfig* allowed) const
35 {
36  assert((useCount == 0) == (config == nullptr));
37  return config && (config != allowed);
38 }
39 
40 
41 // CartridgeSlotManager
43  : motherBoard(motherBoard_)
44  , cartCmd(*this, motherBoard, "cart")
45  , extSlotInfo(motherBoard.getMachineInfoCommand())
46 {
47 }
48 
50 {
51  for (auto slot : xrange(MAX_SLOTS)) {
52  (void)slot;
53  assert(!slots[slot].exists());
54  assert(!slots[slot].used());
55  }
56 }
57 
58 int CartridgeSlotManager::getSlotNum(std::string_view slot)
59 {
60  if (slot.size() == 1) {
61  if (('0' <= slot[0]) && (slot[0] <= '3')) {
62  return slot[0] - '0';
63  } else if (('a' <= slot[0]) && (slot[0] <= 'p')) {
64  return -(1 + slot[0] - 'a');
65  } else if (slot[0] == 'X') {
66  return -256;
67  }
68  } else if (slot.size() == 2) {
69  if ((slot[0] == '?') && ('0' <= slot[1]) && (slot[1] <= '3')) {
70  return slot[1] - '0' - 128;
71  }
72  } else if (slot == "any") {
73  return -256;
74  }
75  throw MSXException("Invalid slot specification: ", slot);
76 }
77 
79 {
80  createExternalSlot(ps, -1);
81 }
82 
84 {
85  if (isExternalSlot(ps, ss, false)) {
86  throw MSXException("Slot is already an external slot.");
87  }
88  for (auto slot : xrange(MAX_SLOTS)) {
89  if (!slots[slot].exists()) {
90  slots[slot].ps = ps;
91  slots[slot].ss = ss;
92  char slotName[] = "carta";
93  slotName[4] += slot;
94  char extName[] = "exta";
95  extName[3] += slot;
96  motherBoard.getMSXCliComm().update(
97  CliComm::HARDWARE, slotName, "add");
98  slots[slot].cartCommand = std::make_unique<CartCmd>(
99  *this, motherBoard, slotName);
100  slots[slot].extCommand = std::make_unique<ExtCmd>(
101  motherBoard, extName);
102  return;
103  }
104  }
105  UNREACHABLE;
106 }
107 
108 
109 int CartridgeSlotManager::getSlot(int ps, int ss) const
110 {
111  for (auto slot : xrange(MAX_SLOTS)) {
112  if (slots[slot].exists() &&
113  (slots[slot].ps == ps) && (slots[slot].ss == ss)) {
114  return slot;
115  }
116  }
117  UNREACHABLE; // was not an external slot
118  return 0; // avoid warning
119 }
120 
122  int ps, const HardwareConfig& allowed) const
123 {
124  testRemoveExternalSlot(ps, -1, allowed);
125 }
126 
128  int ps, int ss, const HardwareConfig& allowed) const
129 {
130  int slot = getSlot(ps, ss);
131  if (slots[slot].used(&allowed)) {
132  throw MSXException("Slot still in use.");
133  }
134 }
135 
137 {
138  removeExternalSlot(ps, -1);
139 }
140 
142 {
143  int slot = getSlot(ps, ss);
144  assert(!slots[slot].used());
145  const auto& slotName = slots[slot].cartCommand->getName();
146  motherBoard.getMSXCliComm().update(
147  CliComm::HARDWARE, slotName, "remove");
148  slots[slot].cartCommand.reset();
149  slots[slot].extCommand.reset();
150 }
151 
152 void CartridgeSlotManager::getSpecificSlot(unsigned slot, int& ps, int& ss) const
153 {
154  assert(slot < MAX_SLOTS);
155  if (!slots[slot].exists()) {
156  throw MSXException("slot-", char('a' + slot), " not defined.");
157  }
158  if (slots[slot].used()) {
159  throw MSXException("slot-", char('a' + slot), " already in use.");
160  }
161  ps = slots[slot].ps;
162  ss = slots[slot].ss;
163 }
164 
166 {
167  assert(slot < MAX_SLOTS);
168  if (!slots[slot].exists()) {
169  throw MSXException("slot-", char('a' + slot), " not defined.");
170  }
171  if (slots[slot].used()) {
172  throw MSXException("slot-", char('a' + slot), " already in use.");
173  }
174  if (slots[slot].ss != -1) {
175  throw MSXException("slot-", char('a' + slot), " is not a primary slot.");
176  }
177  assert(slots[slot].useCount == 0);
178  slots[slot].config = &hwConfig;
179  slots[slot].useCount = 1;
180  return slots[slot].ps;
181 }
182 
183 void CartridgeSlotManager::getAnyFreeSlot(int& ps, int& ss) const
184 {
185  // search for the lowest free slot
186  ps = 4; // mark no free slot
187  for (auto slot : xrange(MAX_SLOTS)) {
188  if (slots[slot].exists() && !slots[slot].used()) {
189  int p = slots[slot].ps;
190  int s = slots[slot].ss;
191  if ((p < ps) || ((p == ps) && (s < ss))) {
192  ps = p;
193  ss = s;
194  }
195  }
196  }
197  if (ps == 4) {
198  throw MSXException("Not enough free cartridge slots");
199  }
200 }
201 
203 {
204  for (auto slot : xrange(MAX_SLOTS)) {
205  if (slots[slot].exists() && (slots[slot].ss == -1) &&
206  !slots[slot].used()) {
207  assert(slots[slot].useCount == 0);
208  slots[slot].config = &hwConfig;
209  slots[slot].useCount = 1;
210  return slots[slot].ps;
211  }
212  }
213  throw MSXException("No free primary slot");
214 }
215 
217  int ps, const HardwareConfig& hwConfig)
218 {
219  int slot = getSlot(ps, -1);
220  assert(slots[slot].config == &hwConfig); (void)hwConfig;
221  assert(slots[slot].useCount == 1);
222  slots[slot].config = nullptr;
223  slots[slot].useCount = 0;
224 }
225 
227  int ps, int ss, const HardwareConfig& hwConfig)
228 {
229  for (auto slot : xrange(MAX_SLOTS)) {
230  if (!slots[slot].exists()) continue;
231  if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
232  if (slots[slot].useCount == 0) {
233  slots[slot].config = &hwConfig;
234  } else {
235  if (slots[slot].config != &hwConfig) {
236  throw MSXException(
237  "Slot ", ps, '-', ss,
238  " already in use by ",
239  slots[slot].config->getName());
240  }
241  }
242  ++slots[slot].useCount;
243  }
244  }
245  // Slot not found, was not an external slot. No problem.
246 }
247 
249  int ps, int ss, const HardwareConfig& hwConfig)
250 {
251  for (auto slot : xrange(MAX_SLOTS)) {
252  if (!slots[slot].exists()) continue;
253  if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
254  assert(slots[slot].config == &hwConfig); (void)hwConfig;
255  assert(slots[slot].useCount > 0);
256  --slots[slot].useCount;
257  if (slots[slot].useCount == 0) {
258  slots[slot].config = nullptr;
259  }
260  return;
261  }
262  }
263  // Slot not found, was not an external slot. No problem.
264 }
265 
266 bool CartridgeSlotManager::isExternalSlot(int ps, int ss, bool convert) const
267 {
268  for (auto slot : xrange(MAX_SLOTS)) {
269  int tmp = (convert && (slots[slot].ss == -1)) ? 0 : slots[slot].ss;
270  if (slots[slot].exists() &&
271  (slots[slot].ps == ps) && (tmp == ss)) {
272  return true;
273  }
274  }
275  return false;
276 }
277 
278 
279 // CartCmd
280 CartridgeSlotManager::CartCmd::CartCmd(
281  CartridgeSlotManager& manager_, MSXMotherBoard& motherBoard_,
282  std::string_view commandName)
283  : RecordedCommand(motherBoard_.getCommandController(),
284  motherBoard_.getStateChangeDistributor(),
285  motherBoard_.getScheduler(),
286  commandName)
287  , manager(manager_)
288  , cliComm(motherBoard_.getMSXCliComm())
289 {
290 }
291 
292 const HardwareConfig* CartridgeSlotManager::CartCmd::getExtensionConfig(
293  std::string_view cartname)
294 {
295  if (cartname.size() != 5) {
296  throw SyntaxError();
297  }
298  int slot = cartname[4] - 'a';
299  return manager.slots[slot].config;
300 }
301 
302 void CartridgeSlotManager::CartCmd::execute(
303  span<const TclObject> tokens, TclObject& result, EmuTime::param /*time*/)
304 {
305  std::string_view cartname = tokens[0].getString();
306 
307  // strip namespace qualification
308  // TODO investigate whether it's a good idea to strip namespace at a
309  // higher level for all commands. How does that interact with
310  // the event recording feature?
311  auto pos = cartname.rfind("::");
312  if (pos != std::string_view::npos) {
313  cartname = cartname.substr(pos + 2);
314  }
315  if (tokens.size() == 1) {
316  // query name of cartridge
317  auto* extConf = getExtensionConfig(cartname);
318  result.addListElement(strCat(cartname, ':'),
319  extConf ? extConf->getName() : string{});
320  if (!extConf) {
321  TclObject options = makeTclList("empty");
322  result.addListElement(options);
323  }
324  } else if (tokens[1] == one_of("eject", "-eject")) {
325  // remove cartridge (or extension)
326  if (tokens[1] == "-eject") {
327  result =
328  "Warning: use of '-eject' is deprecated, "
329  "instead use the 'eject' subcommand";
330  }
331  if (auto* extConf = getExtensionConfig(cartname)) {
332  try {
333  manager.motherBoard.removeExtension(*extConf);
334  cliComm.update(CliComm::MEDIA, cartname, {});
335  } catch (MSXException& e) {
336  throw CommandException("Can't remove cartridge: ",
337  e.getMessage());
338  }
339  }
340  } else {
341  // insert cartridge
342  auto slotname = (cartname.size() == 5)
343  ? cartname.substr(4, 1)
344  : "any";
345  size_t extensionNameToken = 1;
346  if (tokens[1] == "insert") {
347  if (tokens.size() > 2) {
348  extensionNameToken = 2;
349  } else {
350  throw CommandException("Missing argument to insert subcommand");
351  }
352  }
353  auto options = tokens.subspan(extensionNameToken + 1);
354  try {
355  std::string_view romname = tokens[extensionNameToken].getString();
356  auto extension = HardwareConfig::createRomConfig(
357  manager.motherBoard, string(romname), string(slotname), options);
358  if (slotname != "any") {
359  if (auto* extConf = getExtensionConfig(cartname)) {
360  // still a cartridge inserted, (try to) remove it now
361  manager.motherBoard.removeExtension(*extConf);
362  }
363  }
364  result = manager.motherBoard.insertExtension(
365  "ROM", std::move(extension));
366  cliComm.update(CliComm::MEDIA, cartname, romname);
367  } catch (MSXException& e) {
368  throw CommandException(std::move(e).getMessage());
369  }
370  }
371 }
372 
373 string CartridgeSlotManager::CartCmd::help(const vector<string>& tokens) const
374 {
375  return strCat(
376  tokens[0], " eject : remove the ROM cartridge from this slot\n",
377  tokens[0], " insert <filename> : insert ROM cartridge with <filename>\n",
378  tokens[0], " <filename> : insert ROM cartridge with <filename>\n",
379  tokens[0], " : show which ROM cartridge is in this slot\n",
380  "The following options are supported when inserting a cartridge:\n"
381  "-ips <filename> : apply the given IPS patch to the ROM image\n"
382  "-romtype <romtype> : specify the ROM mapper type\n");
383 }
384 
385 void CartridgeSlotManager::CartCmd::tabCompletion(vector<string>& tokens) const
386 {
387  vector<const char*> extra;
388  if (tokens.size() < 3) {
389  extra = { "eject", "insert" };
390  }
391  completeFileName(tokens, userFileContext(), extra);
392 }
393 
394 bool CartridgeSlotManager::CartCmd::needRecord(span<const TclObject> tokens) const
395 {
396  return tokens.size() > 1;
397 }
398 
399 
400 // class CartridgeSlotInfo
401 
402 CartridgeSlotManager::CartridgeSlotInfo::CartridgeSlotInfo(
403  InfoCommand& machineInfoCommand)
404  : InfoTopic(machineInfoCommand, "external_slot")
405 {
406 }
407 
408 void CartridgeSlotManager::CartridgeSlotInfo::execute(
409  span<const TclObject> tokens, TclObject& result) const
410 {
411  checkNumArgs(tokens, Between{2, 3}, Prefix{2}, "?slot?");
412  auto& manager = OUTER(CartridgeSlotManager, extSlotInfo);
413  switch (tokens.size()) {
414  case 2: {
415  // return list of slots
416  string slot = "slotX";
417  for (auto i : xrange(CartridgeSlotManager::MAX_SLOTS)) {
418  if (!manager.slots[i].exists()) continue;
419  slot[4] = char('a' + i);
420  result.addListElement(slot);
421  }
422  break;
423  }
424  case 3: {
425  // return info on a particular slot
426  const auto& slotName = tokens[2].getString();
427  if ((slotName.size() != 5) || (!StringOp::startsWith(slotName, "slot"))) {
428  throw CommandException("Invalid slot name: ", slotName);
429  }
430  unsigned num = slotName[4] - 'a';
431  if (num >= CartridgeSlotManager::MAX_SLOTS) {
432  throw CommandException("Invalid slot name: ", slotName);
433  }
434  auto& slot = manager.slots[num];
435  if (!slot.exists()) {
436  throw CommandException("Slot '", slotName, "' doesn't currently exist in this msx machine.");
437  }
438  result.addListElement(slot.ps);
439  if (slot.ss == -1) {
440  result.addListElement("X");
441  } else {
442  result.addListElement(slot.ss);
443  }
444  if (slot.config) {
445  result.addListElement(slot.config->getName());
446  } else {
447  result.addListElement(std::string_view{});
448  }
449  break;
450  }
451  }
452 }
453 
454 string CartridgeSlotManager::CartridgeSlotInfo::help(
455  const vector<string>& /*tokens*/) const
456 {
457  return "Without argument: show list of available external slots.\n"
458  "With argument: show primary and secondary slot number for "
459  "given external slot.\n";
460 }
461 
462 } // namespace openmsx
openmsx::HardwareConfig
HardwareConfig
Definition: HardwareConfig.cc:459
one_of.hh
HardwareConfig.hh
openmsx::CartridgeSlotManager::getSpecificSlot
void getSpecificSlot(unsigned slot, int &ps, int &ss) const
Definition: CartridgeSlotManager.cc:152
xrange
auto xrange(T e)
Definition: xrange.hh:170
StringOp::startsWith
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:71
openmsx::CartridgeSlotManager::allocateAnyPrimarySlot
int allocateAnyPrimarySlot(const HardwareConfig &hwConfig)
Definition: CartridgeSlotManager.cc:202
openmsx::CartridgeSlotManager::testRemoveExternalSlot
void testRemoveExternalSlot(int ps, const HardwareConfig &allowed) const
Definition: CartridgeSlotManager.cc:121
openmsx::CartridgeSlotManager::getSlotNum
static int getSlotNum(std::string_view slot)
Definition: CartridgeSlotManager.cc:58
openmsx::CartridgeSlotManager::removeExternalSlot
void removeExternalSlot(int ps)
Definition: CartridgeSlotManager.cc:136
TclObject.hh
openmsx::CartridgeSlotManager::freePrimarySlot
void freePrimarySlot(int ps, const HardwareConfig &hwConfig)
Definition: CartridgeSlotManager.cc:216
openmsx::CartridgeSlotManager::isExternalSlot
bool isExternalSlot(int ps, int ss, bool convert) const
Definition: CartridgeSlotManager.cc:266
openmsx::CliComm::update
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
MSXException.hh
openmsx::MSXException
Definition: MSXException.hh:9
openmsx::userFileContext
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:161
openmsx::FileOperations::exists
bool exists(string_view filename)
Does this file (directory) exists?
Definition: FileOperations.cc:675
span::subspan
constexpr subspan_return_t< Offset, Count > subspan() const
Definition: span.hh:266
span
Definition: span.hh:34
OUTER
#define OUTER(type, member)
Definition: outer.hh:41
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::CartridgeSlotManager::getAnyFreeSlot
void getAnyFreeSlot(int &ps, int &ss) const
Definition: CartridgeSlotManager.cc:183
openmsx::CliComm::MEDIA
@ MEDIA
Definition: CliComm.hh:26
openmsx::MSXMotherBoard
Definition: MSXMotherBoard.hh:59
one_of
Definition: one_of.hh:7
openmsx::HardwareConfig::createRomConfig
static std::unique_ptr< HardwareConfig > createRomConfig(MSXMotherBoard &motherBoard, std::string romfile, std::string slotname, span< const TclObject > options)
Definition: HardwareConfig.cc:50
openmsx::yuv2rgb::convert
void convert(const th_ycbcr_buffer &input, RawFrame &output)
Definition: yuv2rgb.cc:341
openmsx::CartridgeSlotManager::CartridgeSlotManager
CartridgeSlotManager(MSXMotherBoard &motherBoard)
Definition: CartridgeSlotManager.cc:42
FileContext.hh
outer.hh
openmsx::CartridgeSlotManager::createExternalSlot
void createExternalSlot(int ps)
Definition: CartridgeSlotManager.cc:78
span::size
constexpr index_type size() const noexcept
Definition: span.hh:296
openmsx::CartridgeSlotManager::freeSlot
void freeSlot(int ps, int ss, const HardwareConfig &hwConfig)
Definition: CartridgeSlotManager.cc:248
StringOp.hh
openmsx::RecordedCommand
Commands that directly influence the MSX state should send and events so that they can be recorded by...
Definition: RecordedCommand.hh:45
openmsx::makeTclList
TclObject makeTclList(Args &&... args)
Definition: TclObject.hh:280
openmsx::HardwareConfig
Definition: HardwareConfig.hh:21
CartridgeSlotManager.hh
openmsx::CartridgeSlotManager::allocateSlot
void allocateSlot(int ps, int ss, const HardwareConfig &hwConfig)
Definition: CartridgeSlotManager.cc:226
openmsx::CartridgeSlotManager::allocateSpecificPrimarySlot
int allocateSpecificPrimarySlot(unsigned slot, const HardwareConfig &hwConfig)
Definition: CartridgeSlotManager.cc:165
openmsx::CartridgeSlotManager::~CartridgeSlotManager
~CartridgeSlotManager()
Definition: CartridgeSlotManager.cc:49
unreachable.hh
CliComm.hh
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
CommandException.hh
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
MSXMotherBoard.hh
openmsx::MSXMotherBoard::getMSXCliComm
CliComm & getMSXCliComm()
Definition: MSXMotherBoard.cc:370
openmsx::CartridgeSlotManager
Definition: CartridgeSlotManager.hh:15
xrange.hh
openmsx::CliComm::HARDWARE
@ HARDWARE
Definition: CliComm.hh:24